<?php

Junction_FileCabinet::using("Junction_Expression_RecursiveDescent");
Junction_FileCabinet::using("Junction_Expression_Grammar");

/**
 * Expression Parser.
 * 
 * <p>This class parses an expression and converts it into
 * a traversable syntax tree.
 * 
 * <p>Expression must adhere to the Junction Expression Grammar
 * defined below.
 * 
 * <code>
 * Expression ::= Predicate { BinaryOp Predicate }
 * Predicate  ::= Literal | "(" Expression ")" | UnaryOp Predicate
 * BinaryOp   ::= "==" | "!=" | "<" | ">" | "<=" | ">="
 * UnaryOp    ::= "!"
 * </code>
 * 
 * <p> TODO: Be sure that only NULLs, properties, or numeric literals exist
 * outside of quotes.
 * 
 * @author Patrick J. Mizer <patrick.mizer@gmail.com>
 * @package junction.expression
 */
 
class Junction_Expression_Parser extends Junction_Expression_RecursiveDescent
{	
	/**
	 * Parse Junction expression
	 * 
	 * @throws Junction_Expression_Exception
	 * @return array expression array
	 */
	public function parse($expression)
	{
		$this->_prepareRawExpression(
			$expression, 
			Junction_Expression_Grammar::getAllTokens());
		
		$this->_parseExpression();
		
		$this->_expect(Junction_Expression_Grammar::$endOfExpressionToken);		
		
		return $this->_getExpressionArray();
	}
	
	/**
	 * Parse the raw expression.  If the expression is invalid
	 * we throw an exception, otherwise the tokenized expression array
	 * is returned.
	 * 
	 * @throws Junction_Expression_Exception
	 * @return void
	 */
	private function _parseExpression()
	{
    	$this->_parsePredicate();
    	
    	while(Junction_Expression_Grammar::isBinaryOperator($this->_next())) {
    		$this->_consume();
    		$this->_parsePredicate();
    	}		
	}
	
	/**
	 * Parse Predicate.
	 * 
	 * TODO is it worth employing a strategy here for each case?
	 * A hash could store the stratgies and map them to the
	 * operator symbols.
	 * 
	 * @throws Junction_Expression_Exception
	 * @return void
	 */
	private function _parsePredicate()
	{
    	if(Junction_Expression_Grammar::isLiteral($this->_next())){
    		$this->_consume();
    	}else if ($this->_next() == "("){
        	$this->_consume();
        	$this->_parseExpression();
        	$this->_expect(")");
    	}else if (Junction_Expression_Grammar::isUnaryOperator($this->_next())){
        	$this->_consume();
         	$this->_parsePredicate();
    	}else{
    		throw new Junction_Expression_Expression('Parse Error!');
    	}		
	}		
}
?>
